#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <direct.h>
#include <sys/stat.h>
#include "czhpacktool.h"
#include "czhpack.h"
#include "list.h"




static char * czhpack_duplicate_string(const char *str)
{
    char * p = malloc((strlen(str)+1)*sizeof(char));
    if(p)
    {
        strcpy(p,str);
    }
    return p;
}

static struct czhpack_item * locate(list *plist,const unsigned char *path)
{
    struct czhpack_item *p = NULL;
    int i,j,k,n;
    int pos;

    if(plist && (path[0]=='/'))
    {
        list_get(plist,0,&p);
        i = 0;
        while(path[i] && (path[i]=='/'))i++;
        while(path[i])
        {
            j = i;
            while(path[j] && (path[j]!='/'))j++;
            if(j > i)
            {
                n = p->length;
                pos = p->offset/sizeof(struct czhpack_item);
                for(k=0;k<n;k++)
                {
                    list_get(plist,pos+k,&p);
                    if(strncmp(p->name, path+i, ((j-i<=CZHPACK_NAME_SIZE)?(j-i):CZHPACK_NAME_SIZE)) == 0)
                    {
                        break;
                    }
                }
                if(k>=n)//not fond
                {
                    return NULL;
                }
            }
            while(path[j] && (path[j]=='/'))j++;
            i = j;
        }
    }
    return p;
}



bool czhpack_pack(const char *destination,const char *source)
{
    char path[1024];
    DIR *dir;
    struct dirent *ent;
    list I;//list of index
    list Q;//list of directories to be process
    char *pparent,*pchild;//path to parent, path to child (path in pack)
    struct czhpack_item *iparent,*ichild;//index of parent, index of child
    struct _stat64 s;
    unsigned int pack_size = 0;

    //1. initiallize
    if(stat64(source,&s)==0)
    {
        if(s.st_mode & _S_IFDIR)
        {
            list_create(&I,sizeof(void *),100);
            list_create(&Q,sizeof(void *),100);
            pparent = czhpack_duplicate_string("/");
            if(!pparent)
            {
                return false;
            }
            if(!list_enque(&Q, &pparent))
            {
                return false;
            }
            iparent = (struct czhpack_item *)malloc(sizeof(struct czhpack_item));
            if(!iparent)
            {
                return false;
            }
            strncpy(iparent->name, CZHPACK_SIGNATURE, CZHPACK_NAME_SIZE);
            iparent->attribute = CZHPACK_ATTRIBUTE_DIRECTORY;
            iparent->offset = 0;
            iparent->length = 0;
            if(!list_append(&I,&iparent))
            {
                return false;
            }
        }
        else
        {
            return false;//source is not a directory
        }
    }
    else
    {
        printf("源目录不存在\n");
        return false;//source not exist
    }

    //2. build index
    while(!list_is_empty(&Q))
    {
        if(!list_deque(&Q, &pparent))
        {
            return false;
        }
        iparent = locate(&I, pparent);
        if(!iparent)
        {
            return false;
        }
        iparent->offset = list_length(&I)*sizeof(struct czhpack_item);
        strcpy(path,source);
        strcat(path,pparent);
        dir = opendir(path);
        if (dir == NULL)
        {
            perror("打开目录出错！\n");
            return false;
        }

        while ((ent = readdir(dir)) != NULL)
        {
            if((strcmp(ent->d_name,".")!=0) && (strcmp(ent->d_name,"..")!=0))
            {
                strcpy(path,source);
                strcat(path,pparent);
                if(strcmp(pparent,"/")!=0)
                {
                    strcat(path,"/");
                }
                strcat(path,ent->d_name);
                if(ent->d_namlen > CZHPACK_NAME_SIZE)//不符合规定
                {
                    return false;
                }
                ichild = (struct czhpack_item *)malloc(sizeof(struct czhpack_item));
                if(!ichild)
                {
                    return false;
                }
                memset(ichild->name, 0, CZHPACK_NAME_SIZE);
                strncpy(ichild->name, ent->d_name, ent->d_namlen);
                if(stat64(path,&s)==0)
                {
                    if(s.st_mode & _S_IFDIR)
                    {
                        ichild->attribute = CZHPACK_ATTRIBUTE_DIRECTORY;
                        ichild->offset = 0;
                        ichild->length = 0;
                        strcpy(path,pparent);
                        if(strcmp(pparent,"/")!=0)
                        {
                            strcat(path,"/");
                        }
                        strcat(path,ent->d_name);
                        pchild = czhpack_duplicate_string(path);
                        if(!pchild)
                        {
                            return false;
                        }
                        if(!list_enque(&Q, &pchild))
                        {
                            return false;
                        }
                    }
                    else
                    {
                        ichild->attribute = 0x00;
                        ichild->offset = 0;//filled in step 3
                        ichild->length = s.st_size;
                    }
                    if(!list_append(&I,&ichild))
                    {
                        return false;
                    }
                    iparent->length++;
                }
                else
                {
                    return false;
                }
            }
        }
        closedir(dir);
        free(pparent);
    }

    //3. revision file item offset
    pack_size = list_length(&I)*sizeof(struct czhpack_item);
    for(int i=0;i<list_length(&I);i++)
    {
        list_get(&I,i,&iparent);
        if(!(iparent->attribute & CZHPACK_ATTRIBUTE_DIRECTORY))//is file
        {
            iparent->offset = pack_size;
            pack_size += iparent->length;
        }
    }

    //4. store index and file data
    {
        FILE *fpo = fopen(destination,"wb");

        if(fpo)
        {
            unsigned char *pbuffer = (unsigned char *)malloc(pack_size);
            if(!pbuffer)
            {
                return false;
            }
            //store index
            memcpy(pbuffer,I.pbuffer,list_length(&I)*sizeof(struct czhpack_item));
            for(int i=0;i<list_length(&I);i++)
            {
                list_get(&I,i,&iparent);
                memcpy(pbuffer+i*sizeof(struct czhpack_item),iparent,sizeof(struct czhpack_item));
            }

            //store file data
            list_reset(&Q);
            pparent = czhpack_duplicate_string("/");
            if(!pparent)
            {
                return false;
            }
            if(!list_enque(&Q, &pparent))
            {
                return false;
            }

            while(!list_is_empty(&Q))
            {
                if(!list_deque(&Q, &pparent))
                {
                    return false;
                }
                iparent = locate(&I, pparent);
                if(!iparent)
                {
                    return false;
                }
                for(int i=0;i<iparent->length;i++)
                {
                    unsigned char name[CZHPACK_NAME_SIZE + 1] = {0};

                    list_get(&I,iparent->offset / sizeof(struct czhpack_item) + i,&ichild);
                    memcpy(name, ichild->name, CZHPACK_NAME_SIZE);
                    name[CZHPACK_NAME_SIZE] = '\0';
                    if(ichild->attribute & CZHPACK_ATTRIBUTE_DIRECTORY)//is directory
                    {
                        strcpy(path, pparent);
                        if(strcmp(pparent,"/")!=0)
                        {
                            strcat(path, "/");
                        }
                        strcat(path, name);
                        pchild = czhpack_duplicate_string(path);
                        if(!pchild)
                        {
                            return false;
                        }
                        if(!list_enque(&Q, &pchild))
                        {
                            return false;
                        }
                    }
                    else
                    {
                        strcpy(path, source);
                        strcat(path, pparent);
                        if(strcmp(pparent,"/")!=0)
                        {
                            strcat(path, "/");
                        }
                        strcat(path, name);
                        
                        if(ichild->length > 0)
                        {
                            FILE *fpi = fopen(path,"rb");
                            if(fpi)
                            {
                                fread(pbuffer + ichild->offset, sizeof(unsigned char), ichild->length, fpi);
                                fclose(fpi);
                            }
                            else
                            {
                                printf("打开数据文件出错！\n");
                                return false;
                            }
                        }
                    }
                }
                free(pparent);
            }
            fwrite(pbuffer,sizeof(unsigned char),pack_size, fpo);
            fclose(fpo);
            free(pbuffer);
        }
        else
        {
            printf("无法写入文件\n");
            return false;
        }
    }

    //5. destroy and free data
    for(int i=0;i<list_length(&I);i++)
    {
        list_get(&I,i,&iparent);
        free(iparent);
    }
    list_destroy(&I);
    list_destroy(&Q);

    return true;
}

bool czhpack_unpack(const char *source,const char *destination)
{
    char path[1024];
    list Q;//list of directories to be process
    char *pparent,*pchild;//path to parent, path to child (path in pack)
    struct czhpack_item *iparent,*ichild;//index of parent, index of child
    struct _stat64 s;
    unsigned int pack_size = 0;
    unsigned char *pbuffer = NULL;

    if(stat64(source,&s)==0)
    {
        FILE *fpi;
        if(s.st_mode & _S_IFDIR || (s.st_size ==0))
        {
            return false;
        }
        pack_size = s.st_size;
        pbuffer = (unsigned char *)malloc(pack_size);
        if(!pbuffer)
        {
            return false;
        }
        fpi = fopen(source,"rb");
        if(fread(pbuffer,sizeof(unsigned char),pack_size,fpi)!=pack_size)
        {
            return false;
        }
        fclose(fpi);

        if(memcmp(pbuffer,CZHPACK_SIGNATURE,CZHPACK_NAME_SIZE))
        {
            printf("非CZHPACK类型\n");
            return false;
        }
    }
    else
    {
        printf("源文件文件不存在\n");
        return false;
    }
    if(stat64(destination,&s)==0)
    {
        if(!(s.st_mode & _S_IFDIR))
        {
            return false;
        }
    }

    list_create(&Q,sizeof(void *),100);
    pparent = czhpack_duplicate_string("/");
    if(!pparent)
    {
        return false;
    }
    if(!list_enque(&Q, &pparent))
    {
        return false;
    }
    mkdir(destination);
    while(!list_is_empty(&Q))
    {
        if(!list_deque(&Q, &pparent))
        {
            return false;
        }
        iparent = czhpack_get(pbuffer, pparent);
        if(!iparent)
        {
            return false;
        }
        for(int i=0;i<iparent->length;i++)
        {
            unsigned char name[CZHPACK_NAME_SIZE + 1] = {0};

            ichild = (struct czhpack_item *)(pbuffer + iparent->offset) + i;
            memcpy(name, ichild->name, CZHPACK_NAME_SIZE);
            name[CZHPACK_NAME_SIZE] = '\0';
            strcpy(path, destination);
            strcat(path, pparent);
            if(strcmp(pparent,"/")!=0)
            {
                strcat(path,"/");
            }
            strcat(path,name);
            if(ichild->attribute & CZHPACK_ATTRIBUTE_DIRECTORY)
            {
                mkdir(path);
                strcpy(path,pparent);
                if(strcmp(pparent,"/")!=0)
                {
                    strcat(path,"/");
                }
                strcat(path,name);
                pchild = czhpack_duplicate_string(path);
                if(!pchild)
                {
                    return false;
                }
                if(!list_enque(&Q, &pchild))
                {
                    return false;
                }
            }
            else
            {
                FILE *fpo = fopen(path,"wb");
                if(fpo)
                {
                    fwrite(pbuffer+ichild->offset,sizeof(unsigned char),ichild->length,fpo);
                    fclose(fpo);
                }
            }
        }
        free(pparent);
    }
    list_destroy(&Q);
    return true;
}